/**
 *  Copyright 2002-2009 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package foo.bar.forum.services;

import foo.bar.forum.domain.*;
import foo.bar.forum.security.RoleType;
import foo.bar.forum.services.SetupType;
import foo.bar.forum.security.UserAccountRealm;
import foo.bar.forum.security.UserAccount;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.meta.MappingTool;
import org.apache.openjpa.lib.util.Options;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.EntityManagerFactory;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;

/**
 * Service that performs one-time, first-time startup initialization procedure upon successfull
 * initialization of Spring's application context.
 * <p/>
 * Initialization procedure includes :-
 * <ul>
 *  <li>Setup the table structure</li>
 *  <li>Setup of data</li>
 * </ul>
 *
 * @author tmjee
 * @version $Date$ $Id$
 */
public class SetupService extends AbstractJpaAwareService implements ApplicationListener, ApplicationContextAware {

    private static final Log LOG = LogFactory.getLog(SetupService.class);


    private TransactionTemplate transactionTemplate;

    private String dbUsername;
    private String dbPassword;
    private String dbUrl;
    private String dbDriverName;
    private boolean addSampleData = true;
    private boolean doTableSetup = true;

    private ApplicationContext applicationContext;


    public SetupService(){}
    public SetupService(JpaTemplate template, TransactionTemplate transactionTemplate) {
        super(template);
        this.transactionTemplate = transactionTemplate;
    }

    public void setDoTableSetup(boolean doTableSetup) { this.doTableSetup = doTableSetup; }
    public void setAddSampleData(boolean addSampleData) { this.addSampleData = addSampleData; }
    public void setUsername(String dbUsername) { this.dbUsername = dbUsername; }
    public void setPassword(String dbPassword) { this.dbPassword = dbPassword; }
    public void setUrl(String dbUrl) { this.dbUrl = dbUrl; }
    public void setDriverClassName(String dbDriverName) { this.dbDriverName = dbDriverName; }

    protected String getUrl() { return dbUrl; }
    protected String getPassword() { return dbPassword; }
    protected String getDriverClassName() { return dbDriverName; }
    protected String getUsername() { return dbUsername; }


    public void onApplicationEvent(ApplicationEvent event) {
        try {
            if (event instanceof ContextRefreshedEvent) {
                onStartup();
            }
            if (event instanceof ContextClosedEvent) {
                onShutdown();
            }
        }
        catch(Throwable e) {
            throw new RuntimeException(e.toString(), e);
        }
    }


    protected void onStartup() throws Throwable {

        // utilities setup
        ServicesUtil.setApplicationContext(applicationContext);

        // table & data setup
        boolean isSetupBefore = isSetupBefore();
        if (!isSetupBefore) {
            if (doTableSetup) {
                doTableSetup();
            }
            if (addSampleData) {
                doDataSetup();
            }
            doneSetup();
        }

        // static data setup
        UserAccountRealm.ANONYMOUS_USER_ACCOUNT = new UserAccount(AnonymousUser.INSTANCE, UserAccountRealm.class.getName()+ "_0");
    }

    protected void onShutdown() {
    }

    public boolean isSetupBefore() {
        Boolean x =  (Boolean) transactionTemplate.execute(new TransactionCallback(){
            public Object doInTransaction(final TransactionStatus transactionStatus) {
                return getJpaTemplate().execute(new JpaCallback(){
                    public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                        try {
                        Query query = entityManager.createNamedQuery("Setup_findByName");
                        query.setParameter("name", SetupType.SETUP_COMPLETED);
                        Setup setup = (Setup) getFirstResult(query);
                        if (setup != null) {
                            return "true".equals(setup.getValue()==null?"":setup.getValue().trim());
                        }
                            return Boolean.FALSE;
                        }
                        catch(Throwable e) {
                            transactionStatus.setRollbackOnly();
                            return Boolean.FALSE;
                        }
                    }
                });
            }
        });
        return x;
    }



    public void doTableSetup() throws IOException, SQLException {
        // db tables setup
        JDBCConfiguration jdbcConfiguration = new JDBCConfigurationImpl();
        jdbcConfiguration.setConnectionURL(dbUrl);
        jdbcConfiguration.setConnectionUserName(dbUsername);
        jdbcConfiguration.setConnectionDriverName(dbDriverName);
        jdbcConfiguration.setConnectionPassword(dbPassword);
        Options opts = new Options();
        String[] args = opts.setFromCmdLine(new String[] {
            "-p", "persistence.xml",
            "-sa", "refresh",      // or drop
            "-dt", "true",
            "-pk", "true",
            "-fk", "true",
            "-ix", "true",
            "-i", "false"

        });
        MappingTool.run(jdbcConfiguration, args, opts);
    }

    


    private User admin = null;
    private User jim = null;
    private User jack = null;
    private User tom = null;
    private User dick = null;





    protected void doDataSetup_users() throws Throwable {
        transactionTemplate.execute(new TransactionCallback(){
            public Object doInTransaction(TransactionStatus transactionStatus) {
                getJpaTemplate().execute(new JpaCallback(){
                    public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                        admin = new User();
                        jim = new User();
                        jack = new User();
                        tom = new User();
                        dick = new User();

                        // administrator
                        admin.setUsername("admin");
                        admin.setDisabled(false);
                        admin.setExpiryDate(null);
                        admin.setLocked(false);
                        admin.setEmail("admin@email.com");
                        admin.setPassword("admin");
                        admin.setPermissions(new ArrayList<Permission>());
                        admin.setProperties(new ArrayList<Property>());
                        admin.setRoles(new ArrayList<Role>() {
                            {
                                Role adminRole = new Role();
                                adminRole.setType(RoleType.ADMINISTRATOR);
                                adminRole.setOwner(admin);
                                add(adminRole);
                            }
                        });
                        saveOrUpdate(entityManager, admin);
                        // jim
                        //      - owner/creator of personalBlog
                        jim.setUsername("jim");
                        jim.setDisabled(false);
                        jim.setExpiryDate(null);
                        jim.setLocked(false);
                        jim.setEmail("jim@email.com");
                        jim.setPassword("jim");
                        jim.setPermissions(new ArrayList<Permission>());
                        jim.setProperties(new ArrayList<Property>());
                        jim.setRoles(new ArrayList<Role>() {
                            {
                                Role regUserRole = new Role();
                                regUserRole.setType(RoleType.REGISTERED_USER);
                                regUserRole.setOwner(jim);
                                add(regUserRole);
                            }
                        });
                        saveOrUpdate(entityManager, jim);

                        // jack
                        //      - member of personalBlog
                        jack.setUsername("jack");
                        jack.setDisabled(false);
                        jack.setExpiryDate(null);
                        jack.setLocked(false);
                        jack.setEmail("jack@email.com");
                        jack.setPassword("jack");
                        jack.setPermissions(new ArrayList<Permission>());
                        jack.setProperties(new ArrayList<Property>());
                        jack.setRoles(new ArrayList<Role>() {
                            {
                                Role regUserRole = new Role();
                                regUserRole.setType(RoleType.REGISTERED_USER);
                                regUserRole.setOwner(jack);
                                add(regUserRole);
                            }
                        });
                        saveOrUpdate(entityManager, jack);

                        // tom
                        //      - moderator of personalBlog
                        tom.setUsername("tom");
                        tom.setDisabled(false);
                        tom.setExpiryDate(null);
                        tom.setLocked(false);
                        tom.setEmail("tom@email.com");
                        tom.setPassword("tom");
                        tom.setPermissions(new ArrayList<Permission>());
                        tom.setProperties(new ArrayList<Property>());
                        tom.setRoles(new ArrayList<Role>() {
                            {
                                Role moderatorUserRole = new Role();
                                moderatorUserRole.setType(RoleType.MODERATOR);
                                moderatorUserRole.setOwner(tom);
                                add(moderatorUserRole);
                            }
                        });
                        saveOrUpdate(entityManager, tom);

                        // dick
                        //      - just a registered user, not associated with personalBlog
                        dick.setUsername("dick");
                        dick.setDisabled(false);
                        dick.setExpiryDate(null);
                        dick.setLocked(false);
                        dick.setEmail("dick@email.com");
                        dick.setPassword("dick");
                        dick.setPermissions(new ArrayList<Permission>());
                        dick.setProperties(new ArrayList<Property>());
                        dick.setRoles(new ArrayList<Role>() {
                            {
                                Role moderatorUserRole = new Role();
                                moderatorUserRole.setType(RoleType.REGISTERED_USER);
                                moderatorUserRole.setOwner(dick);
                                add(moderatorUserRole);
                            }
                        });
                        saveOrUpdate(entityManager, dick);

                        return null;
                    }
                });
                return null;
            }
        });
    }

    



    protected void doDataSetup_cleanup() throws Throwable {
        admin = null;
        jim = null;
        jack = null;
        tom = null;
        dick = null;
    }

    public void doDataSetup() throws Throwable {

    }

    public void doneSetup() throws Throwable {
        transactionTemplate.execute(new TransactionCallback(){
            public Object doInTransaction(TransactionStatus transactionStatus) {
                getJpaTemplate().execute(new JpaCallback(){
                    public Object doInJpa(EntityManager entityManager) throws PersistenceException {
                        Query query = entityManager.createNamedQuery("Setup_findByName");
                        query.setParameter("name", SetupType.SETUP_COMPLETED);
                        Setup setup = (Setup) getFirstResult(query);
                        if (setup == null) {
                            setup = new Setup();
                            setup.setName(SetupType.SETUP_COMPLETED);
                            setup.setValue("true");
                        } else {
                            setup.setValue("true");
                        }
                        saveOrUpdate(entityManager, setup);
                        return null;  
                    }
                });
                return null;
            }
        });
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
